home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / v cisle / sadanastroju / autocomplete_manager-2.3-fx.xpi / chrome / acmanager.jar / content / options.js < prev    next >
Text File  |  2008-03-14  |  37KB  |  954 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the License.
  12.  *
  13.  * The Original Code is the Autocomplete Manager extension.
  14.  *
  15.  * The Initial Developer of the Original Code is
  16.  * Nikitas Liogkas <nikitas@acm.org>.
  17.  * Portions created by the Initial Developer are Copyright (C) 2005-2008
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s): 
  21.  * Version: 2.3
  22.  *
  23.  * ***** END LICENSE BLOCK ***** */
  24.  
  25. // acmanager.js
  26. // implements all the functionality of the options dialog box
  27.  
  28. // global Firefox services
  29. const acm_prefs = Components.classes["@mozilla.org/preferences-service;1"]
  30.                     .getService(Components.interfaces.nsIPrefBranch);
  31.  
  32. const acm_dateService = Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
  33.                           .getService(Components.interfaces.nsIScriptableDateFormat);
  34.  
  35. const acm_mediator = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  36.                        .getService(Components.interfaces.nsIWindowMediator);
  37.  
  38. // option ids; if you change them here, also change them in default/preferences/acmanager.js
  39. const ACM_ACTIVE_COMPONENT    = "extensions.acmanager.active_component";
  40. const ACM_MATCH_BOOKMARKS     = "extensions.acmanager.enhanced.match_bookmarks";
  41. const ACM_MATCH_TITLES        = "extensions.acmanager.enhanced.match_titles";
  42. const ACM_EXCLUDE_LOCAL       = "extensions.acmanager.enhanced.exclude_local";
  43. const ACM_EXCLUDE_SEARCH      = "extensions.acmanager.enhanced.exclude_search";
  44. const ACM_MATCH_ALLADDRESS    = "extensions.acmanager.enhanced.match_alladdress";
  45. const ACM_MATCH_DOMAIN        = "extensions.acmanager.enhanced.match_domain";  // obsolete
  46. const ACM_SORTBY              = "extensions.acmanager.enhanced.sortby";
  47. const ACM_SHOW_TITLES         = "extensions.acmanager.enhanced.show_titles";
  48. const ACM_SHOW_SOURCE         = "extensions.acmanager.enhanced.show_source";  // obsolete
  49. const ACM_SHOW_DATE           = "extensions.acmanager.enhanced.show_date"; 
  50. const ACM_INLINE              = "extensions.acmanager.enhanced.inline";
  51. const ACM_SWAP_COLUMNS        = "extensions.acmanager.enhanced.swap_columns";
  52. const ACM_BOLD_MATCHING        = "extensions.acmanager.enhanced.bold_matching";
  53. const ACM_SHOW_ONARROW        = "extensions.acmanager.enhanced.show_onarrow";
  54. const ACM_MAXROWS             = "extensions.acmanager.enhanced.maxrows";
  55. const ACM_ADDRESS_TRUNC       = "extensions.acmanager.enhanced.address_trunc";
  56. const ACM_TITLE_TRUNC         = "extensions.acmanager.enhanced.title_trunc";
  57. const ACM_BOOKMARKS_FIRST     = "extensions.acmanager.enhanced.bookmarks_first";
  58. const ACM_DEFAULT_SHOW_TITLES = "extensions.acmanager.default.show_titles";
  59. const ACM_DEFAULT_MATCH_TYPED = "browser.urlbar.matchOnlyTyped";
  60. const ACM_DEFAULT_INLINE      = "browser.urlbar.autoFill";
  61. const ACM_DEFAULT_MAXROWS     = "extensions.acmanager.default.maxrows";
  62.  
  63. const ACM_ENHANCED_TABINDEX   = "extensions.acmanager.enhanced.tabindex";
  64.  
  65. // history entry textbox ids; have to match the ones in options.xul
  66. const ACM_HIST_TITLE       = "he_title";
  67. const ACM_HIST_ADDRESS     = "he_address";
  68. const ACM_HIST_FIRST_VISIT = "he_first_visit";
  69. const ACM_HIST_LAST_VISIT  = "he_last_visit";
  70. const ACM_HIST_VISIT_COUNT = "he_visit_count";
  71.     
  72. // history add entry dialog textbox ids; have to match the ones in addEntry.xul
  73. const ACM_ADD_HIST_TITLE       = "add_he_title";
  74. const ACM_ADD_HIST_ADDRESS     = "add_he_address";
  75. const ACM_ADD_HIST_FIRST_VISIT = "add_he_first_visit";
  76. const ACM_ADD_HIST_LAST_VISIT  = "add_he_last_visit";
  77. const ACM_ADD_HIST_VISIT_COUNT = "add_he_visit_count";
  78.  
  79. // history edit entry dialog textbox ids; have to match the ones in editDialog.xul
  80. //const ACM_EDIT_HIST_TITLE       = "edit_he_title";
  81. //const ACM_EDIT_HIST_ADDRESS     = "edit_he_address";
  82. //const ACM_EDIT_HIST_FIRST_VISIT = "edit_he_first_visit";
  83. //const ACM_EDIT_HIST_LAST_VISIT  = "edit_he_last_visit";
  84. //const ACM_EDIT_HIST_VISIT_COUNT = "edit_he_visit_count";
  85.  
  86. // variables to record preferences for use in acm_enforcePreferences(); 
  87. // also used when filtering candidates
  88. var acm_active_component, acm_match_bookmarks, acm_match_titles, acm_exclude_local,
  89.     acm_exclude_search, acm_match_alladdress;
  90.  
  91. // AutocompleteCandidate object arrays
  92. var acm_history = [];            // all displayed items
  93. var acm_addedHistEntries = [];   // new entries added during this invocation of options dialog
  94. var acm_editedHistEntries = [];  // existing entries with modified content
  95. var acm_deletedHistEntries = []; // existing entries to be deleted
  96. var acm_editedURLs = [];         // modified URLs of old entries that have been edited
  97. var acm_addedEntryIndex;         // is an entry new?
  98. var acm_editedEntryIndex;        // has an entry been edited?
  99.  
  100. // history tree
  101. var acm_treeHistory = null;
  102.  
  103. // for the column sorting of the history tree
  104. var acm_histSortCriterion = "URL";
  105. var acm_histSortDirection = false;  // 'true' for ascending, 'false' for descending
  106.  
  107. // the custom view for the history tree
  108. var acm_treeHistoryView = 
  109. {
  110.   rowCount : 0,
  111.   cycleHeader: function(column) { },
  112.   getCellText : function(row, column)
  113.   {
  114.     var text = "";
  115.     if (column.id === "colURL")
  116.       text = acm_history[row].URL;
  117.     else if (column.id === "coltitle")
  118.       text = acm_history[row].title;
  119.  
  120.     return text; 
  121.   },
  122.   getImageSrc : function(row, column){ return null; },
  123.   getRowProperties : function(row, props){ },
  124.   getCellProperties : function(row, column, properties) { },
  125.   getColumnProperties : function(colid, col, props) { },
  126.   isContainer : function(index) { return false; },
  127.   isEditable : function(row, column) { return false; },
  128.   isSeparator : function(index) { return false; },
  129.   isSorted : function(row) { return false; },
  130.   setCellText : function(row, column, value) { },
  131.   setTree: function() { }  
  132. };
  133.  
  134. // returns the specified preference
  135. function acm_getPreference(pref_name)
  136. {
  137.   var pref_type = acm_prefs.getPrefType(pref_name);
  138.   if (pref_type === acm_prefs.PREF_STRING)
  139.       return acm_prefs.getCharPref(pref_name);
  140.   else if (pref_type === acm_prefs.PREF_INT)
  141.       return acm_prefs.getIntPref(pref_name);
  142.   else if (pref_type === acm_prefs.PREF_BOOL)
  143.       return acm_prefs.getBoolPref(pref_name);
  144.   else  // fallback on error
  145.     return acm_prefs.PREF_INVALID;
  146. }
  147.  
  148. // records preferences for later use in acm_enforcePreferences()
  149. function acm_recordPreferences()
  150. {
  151.   acm_active_component = acm_getPreference(ACM_ACTIVE_COMPONENT);
  152.   acm_match_bookmarks  = acm_getPreference(ACM_MATCH_BOOKMARKS);
  153.   acm_match_titles     = acm_getPreference(ACM_MATCH_TITLES);
  154.   acm_exclude_local    = acm_getPreference(ACM_EXCLUDE_LOCAL);
  155.   acm_exclude_search   = acm_getPreference(ACM_EXCLUDE_SEARCH);
  156.   acm_match_alladdress = acm_getPreference(ACM_MATCH_ALLADDRESS);
  157.  
  158.   // restore the previously selected tab
  159.   document.getElementById("acm_tabbox").selectedIndex = acm_getPreference(ACM_ENHANCED_TABINDEX);
  160. }
  161.  
  162. // updates the dependencies for the UI elements
  163. function acm_updateUIDependencies()
  164. {
  165.  
  166.   // disable the corresponding groupbox of options, based on which component is enabled;
  167.   // NOTE: the following doesn't work!?
  168.   // var component_radios = document.getElementById(ACM_ACTIVE_COMPONENT).childNodes;
  169.   if (document.getElementById("acm_enhanced_radio").selected) {
  170.     acm_setEnhancedOptions(true);
  171.     acm_setDefaultOptions(false);
  172.   }
  173.   else if (document.getElementById("acm_default_radio").selected) {
  174.     acm_setEnhancedOptions(false);
  175.     acm_setDefaultOptions(true);
  176.   }
  177.   else {
  178.     acm_setEnhancedOptions(false);
  179.     acm_setDefaultOptions(false);
  180.   }
  181. }
  182.  
  183. // enables/disables the bookmarks position radiogroup
  184. function acm_setBookmarksPosition()
  185. {
  186.   // NOTE: right after the user clicked on the checkbox, 
  187.   // 'document.getElementById(ACM_MATCH_BOOKMARKS).value' gives
  188.   // us the old value of the checkbox (before the click)
  189.   if (document.getElementById(ACM_MATCH_BOOKMARKS).value) 
  190.     document.getElementById(ACM_BOOKMARKS_FIRST).disabled = true;
  191.   else
  192.     document.getElementById(ACM_BOOKMARKS_FIRST).disabled = false;
  193. }
  194.  
  195. // enables/disables the swap columns option
  196. function acm_setSwapColumns()
  197. {
  198.   if (document.getElementById(ACM_SHOW_TITLES).value) 
  199.     document.getElementById(ACM_SWAP_COLUMNS).disabled = true;
  200.   else
  201.     document.getElementById(ACM_SWAP_COLUMNS).disabled = false;
  202. }
  203.  
  204. // enables/disables the options that pertain to the enhanced component
  205. function acm_setEnhancedOptions(flag)
  206. {
  207.   document.getElementById(ACM_MATCH_BOOKMARKS).disabled = !flag;
  208.   document.getElementById(ACM_MATCH_TITLES).disabled = !flag;
  209.   document.getElementById(ACM_EXCLUDE_LOCAL).disabled = !flag;
  210.   document.getElementById(ACM_EXCLUDE_SEARCH).disabled = !flag;
  211.   document.getElementById(ACM_MATCH_ALLADDRESS).disabled = !flag;
  212.  
  213.   document.getElementById(ACM_SORTBY).disabled = !flag;
  214.   document.getElementById(ACM_ADDRESS_TRUNC).disabled = !flag;
  215.   document.getElementById(ACM_TITLE_TRUNC).disabled = !flag;
  216.  
  217.   document.getElementById(ACM_SHOW_TITLES).disabled = !flag;
  218.   document.getElementById(ACM_SHOW_DATE).disabled = !flag;
  219.   document.getElementById(ACM_INLINE).disabled = !flag;
  220.   document.getElementById(ACM_SWAP_COLUMNS).disabled = !flag;
  221.   document.getElementById(ACM_BOLD_MATCHING).disabled = !flag;
  222.   document.getElementById(ACM_SHOW_ONARROW).disabled = !flag;
  223.   document.getElementById(ACM_MAXROWS).disabled = !flag;
  224.  
  225.   // NOTE: 'document.getElementById(ACM_MATCH_BOOKMARKS).checked' doesn't work!?
  226.   if (flag && document.getElementById(ACM_MATCH_BOOKMARKS).value) 
  227.     document.getElementById(ACM_BOOKMARKS_FIRST).disabled = false;
  228.   else
  229.     document.getElementById(ACM_BOOKMARKS_FIRST).disabled = true;
  230.  
  231.   // disable swap columns option if page titles are not to be shown
  232.   if (flag && document.getElementById(ACM_SHOW_TITLES).value) 
  233.     document.getElementById(ACM_SWAP_COLUMNS).disabled = false;
  234.   else
  235.     document.getElementById(ACM_SWAP_COLUMNS).disabled = true;
  236. }
  237.  
  238. // enables/disables the options that pertain to the default component
  239. function acm_setDefaultOptions(flag)
  240. {
  241.   document.getElementById(ACM_DEFAULT_SHOW_TITLES).disabled = !flag;
  242.   document.getElementById(ACM_DEFAULT_MATCH_TYPED).disabled = !flag;
  243.   document.getElementById(ACM_DEFAULT_INLINE).disabled = !flag;
  244.   document.getElementById(ACM_DEFAULT_MAXROWS).disabled = !flag;
  245. }
  246.  
  247. // validates the user-input values for the number of visible suggestions
  248. function acm_validateMaxrowsValues()
  249. {
  250.   // custom component
  251.   var cust_textbox = document.getElementById(ACM_MAXROWS);
  252.   var custom_maxrows_entered = window.parseInt(cust_textbox.value, 10);
  253.   if (custom_maxrows_entered <= 1) {
  254.     alert("The number of visible suggestions has to be greater than one!");
  255.     cust_textbox.value = acm_getPreference(ACM_MAXROWS);
  256.   }
  257.  
  258.   // default component
  259.   var default_textbox = document.getElementById(ACM_DEFAULT_MAXROWS);
  260.   if (default_textbox.value <= 1) {
  261.     alert("The number of visible suggestions has to be greater than one!");
  262.     default_textbox.value = acm_getPreference(ACM_DEFAULT_MAXROWS, "int");
  263.   }
  264. }
  265.  
  266. // enforces user preferences when the options dialog is closed
  267. function acm_enforcePreferences()
  268. {
  269.   this.setCursor("wait");
  270.   // any browser window will do
  271.   var browserWindow = acm_mediator.getMostRecentWindow("navigator:browser");
  272.  
  273.   // save selected tab in enhanced options
  274.   acm_prefs.setIntPref(ACM_ENHANCED_TABINDEX, document.getElementById("acm_tabbox").selectedIndex);
  275.  
  276.   if (document.getElementById("acm_enhanced_radio").selected) {
  277.     // add history items and/or bookmarks if they hadn't been added before
  278.     if (acm_active_component !== "enhanced") {
  279.       if (!browserWindow.acm_Aggregator.includedEntries[ACM_SOURCE_HISTORY])  
  280.         browserWindow.acm_Aggregator.addHistoryEntries();
  281.  
  282.       else if (document.getElementById(ACM_MATCH_BOOKMARKS).value
  283.           && !browserWindow.acm_Aggregator.includedEntries[ACM_SOURCE_BOOKMARKS])
  284.         browserWindow.acm_Aggregator.addBookmarks();  
  285.     }
  286.  
  287.     // include/exclude bookmarks (invalidates caches)
  288.     if (acm_match_bookmarks && !document.getElementById(ACM_MATCH_BOOKMARKS).value) {
  289.       if (browserWindow.acm_Aggregator.includedEntries[ACM_SOURCE_BOOKMARKS])  
  290.         browserWindow.acm_Aggregator.removeBookmarks();
  291.     }
  292.     else if (!acm_match_bookmarks && document.getElementById(ACM_MATCH_BOOKMARKS).value) { 
  293.       if (!browserWindow.acm_Aggregator.includedEntries[ACM_SOURCE_BOOKMARKS])  
  294.         browserWindow.acm_Aggregator.addBookmarks();  
  295.     }
  296.  
  297.     // invalidate caches if there is any difference in the candidate selection options
  298.     var titles_choice     = document.getElementById(ACM_MATCH_TITLES).value;
  299.     var local_choice      = document.getElementById(ACM_EXCLUDE_LOCAL).value;
  300.     var search_choice     = document.getElementById(ACM_EXCLUDE_SEARCH).value;
  301.     var alladdress_choice = document.getElementById(ACM_MATCH_ALLADDRESS).value;
  302.  
  303.     if (acm_match_titles !== titles_choice || acm_exclude_local !== local_choice 
  304.        || acm_exclude_search !== search_choice || acm_match_alladdress !== alladdress_choice) 
  305.       acm_invalidateCaches();
  306.   
  307.     // set acm_maxrows for all open browser windows
  308.     var custom_maxrows_entered = window.parseInt(document.getElementById(ACM_MAXROWS).value, 10);
  309.     var browser_windows = acm_mediator.getEnumerator("navigator:browser");
  310.     while (browser_windows.hasMoreElements()) 
  311.       browser_windows.getNext().acm_maxrows = custom_maxrows_entered;
  312.   }
  313.  
  314.   // set the appropriate location bar for all open browser windows 
  315.   var browser_windows = acm_mediator.getEnumerator("navigator:browser");
  316.   var urlbar;
  317.   while (browser_windows.hasMoreElements()) {
  318.     var nextWindow = browser_windows.getNext();
  319.     if (document.getElementById("acm_default_radio").selected) {
  320.       // set default urlbar's properties
  321.       urlbar = nextWindow.document.getElementById("urlbar");
  322.       urlbar.setAttribute("showcommentcolumn", document.getElementById(ACM_DEFAULT_SHOW_TITLES).value);
  323.       urlbar.setAttribute("maxrows", window.parseInt(document.getElementById(ACM_DEFAULT_MAXROWS).value, 10));
  324.  
  325.       // remove enhanced urlbar's properties
  326.       if (acm_active_component !== "default") 
  327.         nextWindow.acm_removeEnhancedUrlbarProperties();
  328.     }
  329.     else {
  330.       urlbar = nextWindow.document.getElementById("urlbar");
  331.       urlbar.removeAttribute("showcommentcolumn");
  332.       urlbar.removeAttribute("maxrows");
  333.       if (acm_active_component === "default") 
  334.         nextWindow.acm_addEnhancedUrlbarProperties();
  335.     }
  336.   }
  337.  
  338.   // the options dialog has been unloaded
  339.   acm_active_component = null;
  340.  
  341.   // update history file with added, edited, and removed entries; 
  342.   // NOTE: preserve this order to avoid incorrect entry removal from the aggregator; see acm_onEditOK()
  343.   acm_removeDeletedHistEntries();
  344.   //acm_updateEditedHistEntries();
  345.   acm_addNewHistEntries();
  346.  
  347.   this.setCursor("auto");
  348. }
  349.  
  350. // populates the history tree with all history entries
  351. function acm_populateHistoryEntries()
  352. {
  353.   // check if we can get them from acm_Aggregator
  354.   if (acm_active_component !== "enhanced") {
  355.     var getthem = confirm("Retrieving history entries may take some time, depending on the size of \
  356. your history file. If you get a warning about an unresponsive script, just click 'Continue'.\nClick \
  357. 'OK' to retrieve your history entries.");
  358.     if (!getthem)
  359.       return; 
  360.  
  361.     this.setCursor("wait");
  362.     document.getElementById("populateHistEntries").disabled = true; 
  363.  
  364.     acm_entryGenerator = acm_getHistoryItems();
  365.     var nextEntry;
  366.     while (true) {
  367.       nextEntry = acm_entryGenerator.next();
  368.       if (nextEntry !== null) 
  369.         acm_history.push(nextEntry);
  370.       else 
  371.         break;
  372.     }
  373.   }
  374.   else {
  375.     this.setCursor("wait");
  376.     document.getElementById("populateHistEntries").disabled = true; 
  377.     // any browser window will do
  378.     var browserWindow = acm_mediator.getMostRecentWindow("navigator:browser");
  379.     acm_history = browserWindow.acm_Aggregator.allCandidates;
  380.  
  381.     // remove bookmarks if they had been added, and add history duplicates
  382.     if (acm_match_bookmarks) {
  383.       // start from the end of the array for splice to work properly
  384.       for (var i = acm_history.length - 1; i >= 0; i--) {
  385.         if (acm_history[i].source === ACM_SOURCE_BOOKMARKS) 
  386.           acm_history.splice(i, 1);
  387.       }    
  388.  
  389.       acm_history = acm_history.concat(browserWindow.acm_Aggregator.dups);
  390.     }
  391.   }
  392.  
  393.   if (!acm_treeHistory)
  394.     acm_treeHistory = document.getElementById("acm_historyTree");
  395.  
  396.   acm_treeHistoryView.rowCount = acm_history.length;
  397.   acm_treeHistory.treeBoxObject.view = acm_treeHistoryView;
  398.  
  399.   document.getElementById("addHistoryEntry").disabled = false; 
  400.   if (acm_history.length > 0) 
  401.     document.getElementById("removeAllHistoryEntries").disabled = false; 
  402.  
  403.   // NOTE: functional dependency; do this after enabling the buttons
  404.   acm_sortHistoryColumn("URL");
  405.   this.setCursor("auto");
  406. }
  407.  
  408. // sorts history entries according to the given AutocompleteCandidate field,
  409. // while maintaining the current tree selection
  410. function acm_sortHistoryColumn(criterion) 
  411. {
  412.   // return if the tree has not been filled yet
  413.   if (!document.getElementById("populateHistEntries").disabled)
  414.     return;
  415.  
  416.   this.setCursor("wait");
  417.   // find the new and previous sorting columns
  418.   var colNewID      = (criterion === "URL") ? "colURL" : "coltitle";
  419.   var colPreviousID = (acm_histSortCriterion === "URL") ? "colURL" : "coltitle";
  420.   var colNew        = acm_treeHistory.columns[colNewID].element;
  421.   var colPrevious   = acm_treeHistory.columns[colPreviousID].element;
  422.  
  423.   // sort the tree; first time we sort by a column, it is ascending; reverse the sorting for the same column
  424.   acm_histSortDirection = (criterion === acm_histSortCriterion) ? !acm_histSortDirection : true;
  425.   acm_histSortCriterion = criterion;
  426.   acm_sortHistoryTree();
  427.  
  428.   // set the sorting indicator
  429.   var direction = acm_histSortDirection ? "ascending" : "descending";
  430.   if (colNew === colPrevious)
  431.     colNew.setAttribute("sortDirection", direction);
  432.   else {
  433.     colPrevious.removeAttribute("sortDirection");
  434.     colNew.setAttribute("sortDirection", direction);
  435.   } 
  436.   this.setCursor("auto");
  437. }
  438.  
  439. // handles key events on the history tree
  440. function acm_handleKey(event) 
  441. {
  442.   // Del was pressed
  443.   if (event.keyCode === event.DOM_VK_DELETE) 
  444.     acm_removeHistoryEntry();
  445. }
  446.  
  447. // displays the available information for the selected history entry
  448. function acm_historyEntrySelected() 
  449.   // if an entry was selected, and we click on Remove All, the selection remains!
  450.   if (!acm_history.length)
  451.     return;
  452.   
  453.   var selections = acm_getTreeSelections(acm_treeHistory);
  454.  
  455.   // if an entry was selected, enable the Edit and Remove buttons, else clear the information box
  456.   if (selections.length === 1) {
  457.     //document.getElementById("editHistoryEntry").disabled = false;
  458.     document.getElementById("removeHistoryEntry").disabled = false;
  459.   }
  460.   else if (selections.length > 1) {
  461.     //document.getElementById("editHistoryEntry").disabled = true;
  462.     document.getElementById("removeHistoryEntry").disabled = false;
  463.   }
  464.   else 
  465.     acm_clearHistInfoBox();
  466.  
  467.   // clear the information box if multiple entries are selected
  468.   if (selections.length > 1) {
  469.     acm_clearHistInfoBox();
  470.     return;
  471.   }
  472.     
  473.   var index = selections[0];
  474.  
  475.   // associate textboxes with AutocompleteCandidate fields
  476.   var hist_textbox_props = 
  477.   [
  478.     {id: ACM_HIST_TITLE, value: acm_history[index].title},
  479.     {id: ACM_HIST_ADDRESS, value: acm_history[index].URL}, 
  480.     {id: ACM_HIST_FIRST_VISIT, value: acm_history[index].first_visit}, 
  481.     {id: ACM_HIST_LAST_VISIT, value: acm_history[index].last_visit},
  482.     {id: ACM_HIST_VISIT_COUNT, value: acm_history[index].visit_count}
  483.   ];
  484.  
  485.   // update the values of the textboxes
  486.   var textbox;
  487.   for (var i = 0, len = hist_textbox_props.length; i < len; i++) {
  488.     var id = hist_textbox_props[i].id;
  489.     textbox = document.getElementById(id);
  490.     if ( (id === ACM_HIST_FIRST_VISIT) || (id === ACM_HIST_LAST_VISIT) ) 
  491.       textbox.value = acm_micros2date(hist_textbox_props[i].value);
  492.     else
  493.       textbox.value = hist_textbox_props[i].value;
  494.   }  
  495. }
  496.  
  497. // clears the history information box
  498. function acm_clearHistInfoBox() 
  499. {
  500.   document.getElementById(ACM_HIST_TITLE).value       = "";
  501.   document.getElementById(ACM_HIST_ADDRESS).value     = "";
  502.   document.getElementById(ACM_HIST_FIRST_VISIT).value = "";
  503.   document.getElementById(ACM_HIST_LAST_VISIT).value  = "";
  504.   document.getElementById(ACM_HIST_VISIT_COUNT).value = "";
  505. }
  506.  
  507. // converts microsecs to a formatted date
  508. function acm_micros2date(microsecs) 
  509. {
  510.   if (window.isNaN(microsecs) || (microsecs === 0))
  511.     return "";
  512.   else {
  513.     var d = new Date(microsecs/1000);
  514.     return acm_dateService.FormatDateTime(
  515.     "", acm_dateService.dateFormatShort, 
  516.         acm_dateService.timeFormatNoSecondsForce24Hour, 
  517.         d.getFullYear(), d.getMonth() + 1, // month is 1-12
  518.         d.getDate(), d.getHours(), 
  519.         d.getMinutes(), d.getSeconds());
  520.   }
  521. }
  522.  
  523. // converts a formatted date to microseconds; returns NaN if the dateString is malformed
  524. function acm_date2micros(dateString) 
  525. {
  526.   var d = new Date(dateString);
  527.   d.setMonth(d.getMonth() + 1);   // month is 1-12
  528.   return Date.parse(d.toLocaleString()) * 1000;
  529. }
  530.  
  531. // saves the new entry that was input by the user in the Add History Entry dialog box
  532. function acm_onAddOK()
  533. {
  534.   this.setCursor("wait");
  535.  
  536.   // get user input
  537.   var title = document.getElementById(ACM_ADD_HIST_TITLE).value;
  538.   var address = document.getElementById(ACM_ADD_HIST_ADDRESS).value;
  539.   //var firstvisit = acm_date2micros(document.getElementById(ACM_ADD_HIST_FIRST_VISIT).value);
  540.   //var lastvisit = acm_date2micros(document.getElementById(ACM_ADD_HIST_LAST_VISIT).value);
  541.   //var visitcount = window.parseInt(document.getElementById(ACM_ADD_HIST_VISIT_COUNT).value, 10); 
  542.  
  543.   // set new entry properties
  544.   if (!address) {
  545.     alert("You have to specify an address!");
  546.     return;
  547.   }
  548.  
  549.   var protocolSpecified = (address.search("^[a-z]*://") !== -1)
  550.   if (!protocolSpecified)
  551.     address = "http://" + address;
  552.  
  553.   //if (window.isNaN(firstvisit)) {
  554.   //  if (document.getElementById(ACM_ADD_HIST_FIRST_VISIT).value)
  555.   //    alert("First Visit Date is malformed!");
  556.   //  firstvisit = 0;
  557.   //}
  558.  
  559.   //if (window.isNaN(lastvisit)) {
  560.   //  if (document.getElementById(ACM_ADD_HIST_LAST_VISIT).value)
  561.   //    alert("Last Visit Date is malformed!");
  562.   //  lastvisit = 0;
  563.   //}
  564.  
  565.   //if (window.isNaN(visitcount)) {
  566.   //  if (document.getElementById(ACM_ADD_HIST_VISIT_COUNT).value)
  567.   //    alert("Visit Count is invalid!");
  568.   //  visitcount = 0;
  569.   //} 
  570.  
  571.   //var newEntry = new AutocompleteCandidate(opener.acm_history.length, title, address, 
  572.   //  firstvisit, lastvisit, visitcount, ACM_SOURCE_HISTORY); 
  573.  
  574.   var newEntry = new AutocompleteCandidate(opener.acm_history.length, title, address, 
  575.     0, 0, "", 0, null, ACM_SOURCE_HISTORY); 
  576.  
  577.   opener.acm_addedHistEntries.push(newEntry);
  578.  
  579.   // insert at the correct position in the tree
  580.   var rowChanged;  // the first of two rows we are going to change
  581.  
  582.   if (!opener.acm_history.length) {  // empty history file
  583.     opener.acm_history.push(newEntry);
  584.     rowChanged = 0;
  585.   }
  586.  
  587.   else {  
  588.     // place at the beginning of array
  589.     if (acm_alphaHistorySortFun(newEntry, opener.acm_history[0]) <= 0) {
  590.       opener.acm_history.splice(0, 1, newEntry, opener.acm_history[0]);
  591.       rowChanged = 0;
  592.     }
  593.  
  594.     // place at the end of array
  595.     else if (acm_alphaHistorySortFun(newEntry, opener.acm_history[opener.acm_history.length - 1]) >= 0) {
  596.       opener.acm_history.splice(opener.acm_history.length - 1, 1, 
  597.         opener.acm_history[opener.acm_history.length - 1], newEntry);
  598.       rowChanged = opener.acm_history.length - 2;
  599.     }
  600.  
  601.     // place in the middle
  602.     else {  
  603.       for (var i = 0; i <= opener.acm_history.length - 2; i++) {
  604.         if (acm_alphaHistorySortFun(newEntry, opener.acm_history[i]) >= 0
  605.             && acm_alphaHistorySortFun(newEntry, opener.acm_history[i+1]) <= 0) {
  606.           opener.acm_history.splice(i, 1, opener.acm_history[i], newEntry);
  607.           rowChanged = i; 
  608.           break;
  609.         }
  610.       }
  611.     }
  612.   }
  613.  
  614.   // enable buttons
  615.   //opener.document.getElementById("editHistoryEntry").disabled = false;
  616.   opener.document.getElementById("removeHistoryEntry").disabled = false;
  617.   opener.document.getElementById("removeAllHistoryEntries").disabled = false;
  618.  
  619.   // add new entry to the history tree 
  620.   opener.acm_treeHistory.view.selection.selectEventsSuppressed = true;
  621.   opener.acm_treeHistoryView.rowCount += 1;
  622.   if (acm_history.length) {
  623.     opener.acm_treeHistory.treeBoxObject.rowCountChanged(rowChanged, -1);
  624.     opener.acm_treeHistory.treeBoxObject.rowCountChanged(rowChanged, 2);
  625.   }
  626.   else
  627.     opener.acm_treeHistory.treeBoxObject.rowCountChanged(rowChanged, 1);
  628.  
  629.   var rowNewItem = (rowChanged === 0) ? 0 : rowChanged + 1;
  630.   opener.acm_treeHistory.treeBoxObject.ensureRowIsVisible(rowNewItem);
  631.   opener.acm_treeHistory.view.selection.select(rowNewItem);
  632.   opener.acm_treeHistory.view.selection.selectEventsSuppressed = false;
  633.  
  634.   this.setCursor("auto");
  635. }
  636.  
  637. // fills in the relevant information in the Edit Entry dialog box
  638. function acm_setupEditDialog()
  639. {
  640.   var selections = acm_getTreeSelections(opener.document.getElementById("acm_historyTree"));
  641.  
  642.   // only one entry should be selected when we get here
  643.   if (selections.length > 1) {
  644.     alert("More than one entry is selected!");
  645.     return;
  646.   }
  647.  
  648.   var index = selections[0];
  649.  
  650.   // associate textboxes with AutocompleteCandidate fields
  651.   var edit_hist_textbox_props = 
  652.   [
  653.     {id: ACM_EDIT_HIST_TITLE, value: opener.acm_history[index].title},
  654.     {id: ACM_EDIT_HIST_ADDRESS, value: opener.acm_history[index].URL}, 
  655.     {id: ACM_EDIT_HIST_FIRST_VISIT, value: opener.acm_history[index].first_visit}, 
  656.     {id: ACM_EDIT_HIST_LAST_VISIT, value: opener.acm_history[index].last_visit},
  657.     {id: ACM_EDIT_HIST_VISIT_COUNT, value: opener.acm_history[index].visit_count}
  658.   ];
  659.  
  660.   // update the values of the textboxes
  661.   var textbox;
  662.   for (var i = 0, len = edit_hist_textbox_props.length; i < len; i++) {
  663.     var id = edit_hist_textbox_props[i].id;
  664.     textbox = document.getElementById(id);
  665.     if ( (id === ACM_EDIT_HIST_FIRST_VISIT) || (id === ACM_EDIT_HIST_LAST_VISIT) ) 
  666.       textbox.value = acm_micros2date(edit_hist_textbox_props[i].value)
  667.     else
  668.       textbox.value = edit_hist_textbox_props[i].value;
  669.   }
  670.  
  671.   // check if this is a newly added entry
  672.   acm_addedEntryIndex = -1;
  673.   for (var i = 0, len = opener.acm_addedHistEntries.length; i < len; i++) {
  674.     if (opener.acm_addedHistEntries[i].URL === opener.acm_history[index].URL) {
  675.       acm_addedEntryIndex = i;
  676.       break;
  677.     } 
  678.   }
  679.  
  680.   // check if this is an edited entry
  681.   acm_editedEntryIndex = -1;
  682.   for (var i = 0, len = opener.acm_editedHistEntries.length; i < len; i++) {
  683.     if (opener.acm_editedHistEntries[i].URL === opener.acm_history[index].URL) {
  684.       acm_editedEntryIndex = i;
  685.       break;
  686.     } 
  687.   }
  688. }
  689.  
  690. // saves the changes that were input by the user in the Edit Entry dialog box
  691. function acm_onEditOK()
  692. {
  693.   var selections = acm_getTreeSelections(opener.document.getElementById("acm_historyTree"));
  694.   var index = selections[0];
  695.   var textbox;
  696.  
  697.   textbox = document.getElementById(ACM_EDIT_HIST_ADDRESS);
  698.   if (!textbox.value) {
  699.     alert("You have to at least specify the address!");
  700.     return;
  701.   }
  702.  
  703.   // update entry properties
  704.   var protocolSpecified = (textbox.value.search("^[a-z]*://") !== -1)
  705.   var newURL;
  706.   if (protocolSpecified)
  707.     newURL = textbox.value;
  708.   else
  709.     newURL = "http://" + textbox.value;
  710.  
  711.   // save old URL to remove it from the aggregator list later
  712.   var oldURL = null;
  713.   if (newURL !== opener.acm_history[index].URL)
  714.     oldURL = opener.acm_history[index].URL;
  715.  
  716.   opener.acm_history[index].URL = newURL;
  717.  
  718.   textbox = document.getElementById(ACM_EDIT_HIST_TITLE);
  719.   opener.acm_history[index].title = textbox.value;
  720.  
  721.   textbox = document.getElementById(ACM_EDIT_HIST_FIRST_VISIT);
  722.   var firstvisit = acm_date2micros(textbox.value);
  723.   if (window.isNaN(firstvisit)) {
  724.     if (textbox.value)
  725.       alert("First Visit Date is malformed!");
  726.   }
  727.   else
  728.     opener.acm_history[index].first_visit = firstvisit;
  729.  
  730.   textbox = document.getElementById(ACM_EDIT_HIST_LAST_VISIT);
  731.   var lastvisit = acm_date2micros(textbox.value);
  732.   if (window.isNaN(lastvisit)) {
  733.     if (textbox.value)
  734.       alert("Last Visit Date is malformed!");
  735.   }
  736.   else
  737.     opener.acm_history[index].last_visit = lastvisit;
  738.  
  739.   textbox = document.getElementById(ACM_EDIT_HIST_VISIT_COUNT);
  740.   var visitcount = window.parseInt(textbox.value, 10);
  741.   if (window.isNaN(visitcount)) {
  742.     if (textbox.value)
  743.       alert("Visit Count is invalid!");
  744.   }
  745.   else
  746.     opener.acm_history[index].visit_count = visitcount;
  747.  
  748.   // remove the edited entry from the main array
  749.   var newEntry = opener.acm_history[index];
  750.   opener.acm_history.splice(index, 1);
  751.  
  752.   // insert at the correct position in the tree
  753.   var rowChanged = -1;  // first of the two rows we are going to change
  754.   if (opener.acm_histSortDirection) {
  755.     // place at the beginning of array
  756.     if (newEntry[opener.acm_histSortCriterion] <= opener.acm_history[0][opener.acm_histSortCriterion]) {
  757.       opener.acm_history.splice(0, 1, newEntry, opener.acm_history[0]);
  758.       rowChanged = 0;
  759.     }
  760.  
  761.     // place at the end of array
  762.     if (newEntry[opener.acm_histSortCriterion] >= 
  763.           opener.acm_history[opener.acm_history.length - 1][opener.acm_histSortCriterion]) {
  764.       opener.acm_history.splice(opener.acm_history.length - 1, 1, opener.acm_history[0], newEntry);
  765.       rowChanged = opener.acm_history.length;
  766.     }
  767.  
  768.     if (rowChanged === -1) {
  769.       for (var i = 0; i <= opener.acm_history.length - 2; i++) {
  770.         if ( (newEntry[opener.acm_histSortCriterion] >= opener.acm_history[i][opener.acm_histSortCriterion])
  771.              && (newEntry[opener.acm_histSortCriterion] <= opener.acm_history[i+1][opener.acm_histSortCriterion]) ) {
  772.           opener.acm_history.splice(i, 1, opener.acm_history[i], newEntry);
  773.           rowChanged = i; 
  774.           break;
  775.         }
  776.       }
  777.     }
  778.   }
  779.  
  780.   else 
  781.   {
  782.     // place at the beginning of array
  783.     if (newEntry[opener.acm_histSortCriterion] >= opener.acm_history[0][opener.acm_histSortCriterion]) {
  784.       opener.acm_history.splice(0, 1, newEntry, opener.acm_history[0]);
  785.       rowChanged = 0;
  786.     }
  787.  
  788.     // place at the end of array
  789.     if (newEntry[opener.acm_histSortCriterion] <= 
  790.           opener.acm_history[opener.acm_history.length - 1][opener.acm_histSortCriterion]) {
  791.       opener.acm_history.splice(opener.acm_history.length - 1, 1, opener.acm_history[0], newEntry);
  792.       rowChanged = opener.acm_history.length;
  793.     }
  794.  
  795.     if (rowChanged === -1) {
  796.       for (var i = 0; i <= opener.acm_history.length - 2; i++) {
  797.         if ( (newEntry[opener.acm_histSortCriterion] <= opener.acm_history[i][opener.acm_histSortCriterion])
  798.              && (newEntry[opener.acm_histSortCriterion] >= opener.acm_history[i+1][opener.acm_histSortCriterion]) ) {
  799.           opener.acm_history.splice(i, 1, opener.acm_history[i], newEntry);
  800.           rowChanged = i; 
  801.           break;
  802.         }
  803.       }
  804.     }
  805.   }
  806.  
  807.   // add to the appropriate array
  808.   if (acm_addedEntryIndex === -1)  { // old entry
  809.     if (acm_editedEntryIndex === -1) { // not an already edited entry
  810.       opener.acm_editedHistEntries.push(newEntry); 
  811.       if (oldURL)  // URL has been modified
  812.         opener.acm_editedURLs.push(oldURL); 
  813.     }
  814.     else  { // edited entry
  815.       opener.acm_editedHistEntries.splice(acm_editedEntryIndex, 1);  
  816.       opener.acm_editedHistEntries.push(newEntry);  
  817.       // URL has been modified; normally we should remove the old oldURL,
  818.       // to avoid incorrect removal of that URL from the aggregator; nevertheless,
  819.       // this is taken care of if we preserve the order of write-back
  820.       // calls in acm_onOK(), since the URL will be eventually added if necessary!
  821.       if (oldURL) 
  822.         opener.acm_editedURLs.push(oldURL); 
  823.     }
  824.   }
  825.   else {  // new entry
  826.     opener.acm_addedEntries.splice(acm_addedEntryIndex, 1);
  827.     opener.acm_addedHistEntries.push(newEntry);
  828.   }
  829.  
  830.   // update the tree view
  831.   var rowNewItem = (rowChanged === 0) ? 0 : rowChanged + 1;
  832.   opener.acm_treeHistory.view.selection.select(0);
  833.   opener.acm_treeHistory.view.selection.select(rowNewItem);  // reselect to update info box
  834.   opener.acm_treeHistory.treeBoxObject.ensureRowIsVisible(rowNewItem);
  835. }
  836.  
  837. // removes selected history entries
  838. function acm_removeHistoryEntry() 
  839. {
  840.   // disable tree selection notifications during the deletion
  841.   acm_treeHistory.view.selection.selectEventsSuppressed = true;
  842.  
  843.   // place deleted items into deletedHistEntries array, and set them to null in the acm_history array
  844.   var selections = acm_getTreeSelections(acm_treeHistory);
  845.  
  846.   for (var i = selections.length - 1; i >= 0; i--) {
  847.     // check if this is a newly-added entry
  848.     acm_addedEntryIndex = -1;
  849.     for (var j = 0, len = acm_addedHistEntries.length; j < len; j++) {
  850.       if (acm_addedHistEntries[j].URL === acm_history[selections[i]].URL) {
  851.         acm_addedEntryIndex = j;
  852.         break;
  853.       } 
  854.     }
  855.  
  856.     // check if this is an edited entry
  857.     acm_editedEntryIndex = -1;
  858.     //for (var j = 0, len = acm_editedHistEntries.length; j < len; j++) {
  859.     //  if (acm_editedHistEntries[j].URL === acm_history[selections[i]].URL) {
  860.     //    acm_editedEntryIndex = j;
  861.     //    break;
  862.     //  } 
  863.     //} 
  864.  
  865.     // add to the appropriate array
  866.     if (acm_addedEntryIndex === -1)  { // old entry
  867.       if (acm_editedEntryIndex === -1)  // not an already-edited entry
  868.         acm_deletedHistEntries.push(acm_history[selections[i]]);
  869.       else  { // edited entry
  870.         acm_editedHistEntries.splice(acm_editedEntryIndex, 1);  
  871.         acm_deletedHistEntries.push(acm_history[selections[i]]);
  872.       }
  873.     }
  874.     else  // new entry
  875.       acm_addedHistEntries.splice(acm_addedEntryIndex, 1); 
  876.  
  877.     acm_history[selections[i]] = null;
  878.   }
  879.  
  880.   // clean up original array by removing all the null entries
  881.   for (var tail = acm_history.length - 1; tail >= 0; tail--) {
  882.     if (acm_history[tail] === null) {
  883.       var head = tail;  // find consecutive deleted selections
  884.       while ( (head >= 0) && (acm_history[head] === null) ) 
  885.         head--;
  886.  
  887.       acm_history.splice(head + 1, tail - head);
  888.       acm_treeHistoryView.rowCount -= (tail - head);
  889.       acm_treeHistory.treeBoxObject.rowCountChanged(head + 1, head - tail);
  890.     }
  891.   }
  892.  
  893.   // update selection or buttons
  894.   if (acm_history.length) {
  895.     // update selection and tree view
  896.     var nextEntry = (selections[0] < acm_history.length) ? 
  897.       selections[0] : acm_history.length - 1;
  898.     acm_treeHistory.view.selection.select(nextEntry);
  899.     acm_treeHistory.treeBoxObject.ensureRowIsVisible(nextEntry);
  900.   } 
  901.   else {
  902.     // disable buttons
  903.     //document.getElementById("editHistoryEntry").disabled = true;
  904.     document.getElementById("removeHistoryEntry").disabled = true;
  905.     document.getElementById("removeAllHistoryEntries").disabled = true;
  906.  
  907.     acm_clearHistInfoBox(); 
  908.   }
  909.  
  910.   // reenable tree selection notifications after the deletion
  911.   acm_treeHistory.view.selection.selectEventsSuppressed = false;
  912. }
  913.  
  914. // removes all history entries
  915. function acm_removeAllHistEntries() 
  916. {
  917.   var answer = confirm("Are you sure you want to remove all history entries?")
  918.   if (answer) {
  919.     for (var i = 0, lenHistory = acm_history.length; i < lenHistory; i++) {
  920.       // check if this is a newly-added entry
  921.       acm_addedEntryIndex = -1;
  922.       for (var j = 0, lenAddedEntries = acm_addedHistEntries.length; j < lenAddedEntries; j++) {
  923.         if (acm_addedHistEntries[j].URL === acm_history[i].URL) {
  924.           acm_addedEntryIndex = j;
  925.           break;
  926.         } 
  927.       }
  928.  
  929.       // only mark old entries for deletion
  930.       if (acm_addedEntryIndex === -1)  
  931.         acm_deletedHistEntries.push(acm_history[i]);
  932.     }
  933.  
  934.     // clear arrays
  935.     acm_addedHistEntries = [];
  936.     //acm_editedHistEntries = [];
  937.     //acm_editedURLs = []; 
  938.     acm_history = [];
  939.  
  940.     // update the tree view 
  941.     var oldCount = acm_treeHistoryView.rowCount;
  942.     acm_treeHistoryView.rowCount = 0;
  943.     acm_treeHistory.treeBoxObject.rowCountChanged(0, -oldCount);
  944.  
  945.     // disable buttons
  946.     //document.getElementById("editHistoryEntry").disabled = true;
  947.     document.getElementById("removeHistoryEntry").disabled = true;
  948.     document.getElementById("removeAllHistoryEntries").disabled = true;
  949.  
  950.     acm_clearHistInfoBox(); 
  951.   }
  952. }
  953.